/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package tubes5progin.client.clientSocketHandler;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import tools.constants;
import tools.debug;
import tools.directoryHelper;
import tools.fileProcessor;
import tools.socketHelper;
import tubes5progin.client.folderstructure.filesystem;
import tubes5progin.server.SqlAccess;

/**
 *
 * @author Rezha
 */
public class clientHandler implements Runnable {

    //tipe client
    public int tipeClient;
    public String username;
    public String password;
    //socket variable
    public Socket connection;
    public String serverAddr;
    public int port;
    public DataInputStream reader;
    public DataOutputStream writer;
    //file variable
    public File parentDir;
    public filesystem clientFS;
    //connect attempt
    public int connectAttemp = 0;

    //konstruktor
    public clientHandler(int tipe, String username, String password, File parentDir, String serverAddr, int port) {
        this.tipeClient = tipe;
        this.username = username;
        this.password = password;
        this.serverAddr = serverAddr;
        this.port = port;
        this.parentDir = parentDir;
        directoryHelper dh = new directoryHelper(parentDir);
        this.clientFS = dh.getListFileSystem();
    }

    //another constructor
    public clientHandler(int tipe, String username, String password, String serverAddr, int port) {
        this.tipeClient = tipe;
        this.username = username;
        this.password = password;
        this.serverAddr = serverAddr;
        this.port = port;
    }

    public void createReaderWriter() {
        if (this.connection != null) {
            try {
                this.reader = socketHelper.getReader(this.connection);
                this.writer = socketHelper.getWriter(connection);
            } catch (Exception ex) {
                Logger.getLogger(clientHandler.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public void close() {
        try {

            this.connection.close();
            this.reader.close();
            this.writer.close();
        } catch (IOException ex) {
            debug.debugCode("IO Exception Occured in Class Client Handler Trying to Close Connection");
        }
    }

    public String startCommunication() throws Exception {
        String ret = "";
        try {
            //bikin koneksi
            this.connection = new Socket(this.serverAddr, this.port);
            //bikin reader dan writer
            createReaderWriter();
            this.connection.setSoTimeout(constants.timeout);
            if (this.reader != null && this.writer != null) {
                if (this.tipeClient == constants.LOGIN_MODE) {
                    return login();
                } else if (this.tipeClient == constants.SYNC_MODE) {
                    sync();
                } else if (this.tipeClient == constants.DOWNLOAD_MODE) {
                    download();
                } else if (this.tipeClient == constants.UPLOAD_MODE) {
                    upload();
                }
            }
        } catch (SocketException ex) {
            //berarti time out
            //reconnect
            if (connectAttemp++ < constants.maxConnectAttemp) {
                debug.debugCode("Client Time Out Socket Exception Occured, Retry");
                startCommunication();
            } else {
                throw new Exception("Max Connection Attemp Reached, Check your Internet Setting");
            }
        } catch (UnknownHostException ex) {
            //Logger.getLogger(clientHandler.class.getName()).log(Level.SEVERE, null, ex);
            if (connectAttemp++ < constants.maxConnectAttemp) {
                debug.debugCode("Unknown Host Occured, Retry");
                startCommunication();
            } else {
                throw new Exception("Max Connection Attemp Reached Host Not found, check Your Internet setting");
            }

        } catch (IOException ex) {
            //Logger.getLogger(clientHandler.class.getName()).log(Level.SEVERE, null, ex);
            if (connectAttemp++ < constants.maxConnectAttemp) {
                debug.debugCode("IO Client Exception Occured, Retry");
                startCommunication();
            } else {
                throw new Exception("Max Connection Attemp Reached IO exception Problem, check your port setting");
            }

        }
        close();
        return ret;
    }

    // <editor-fold defaultstate="collapsed" desc="KOMUNIKASI DENGAN SERVER">
    public String login() throws SocketException, Exception {
        String ret = "";
        try {
            protocolHandler ph = new protocolHandler(constants.LOGIN_MODE);

            //kirim pesan pertama kali ke server
            debug.debugCode("Mengirim Pesan Pertama Ke Server yaitu: " + constants.clientLoginMode);
            this.writer.writeUTF(constants.clientLoginMode);
            this.writer.flush();
            //baca balasan dari server
            debug.debugCode("Menunggu Balasan Dari Server");
            String firstReply = this.reader.readUTF();
            debug.debugCode("Menerima Pesan Dari Server: " + firstReply);
            //cek reply
            if (firstReply.equals(constants.serverReady)) {
                //kirim login dan password --> nanti aja, gampanglah
                this.writer.writeUTF(this.username + "#" + this.password);
                this.writer.flush();
                //System.out.print(this.username + "#" + this.password);
                //baca pesan
                //ret = this.reader.readUTF();
                //return ret;
            } else {
                ret = "Server not ready, trying to reconnect";
                debug.debugCode(ret);
                close();
                startCommunication();
            }
        } catch (IOException ex) {
            ret = "IOException Occured";
            debug.debugCode(ret);
            close();
            startCommunication();
            Logger.getLogger(clientHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
        return ret;
    }

    private String sync() throws SocketException, IOException, Exception {
        //keterangan
        //fungsi ini dipanggil hanya pada saat pertama kali menjalankan gui client
        //ketika sinkronisasi awal telah berhasil dijalankan,
        //maka akan dibuat sebuah thread baru yang akan mengecek apakah ada perubahan pada client
        //jika ada perubahan maka, thread tersebut akan mengupdate file tersebut ke server
        String ret = "";
        try {
            //kirim pesan pertama kali ke server
            debug.debugCode("Mengirim Pesan Pertama Ke Server yaitu: " + constants.clientSyncMode);
            this.writer.writeUTF(constants.clientSyncMode);
            this.writer.flush();
            //baca balasan dari server
            debug.debugCode("Menunggu Balasan Dari Server");
            String firstReply = this.reader.readUTF();
            debug.debugCode("Menerima Pesan Dari Server: " + firstReply);
            //cek reply
            if (firstReply.equals(constants.serverReady)) {
                //mengirim data file yang akan dikirim
                //mengirim username
                this.writer.writeUTF(this.username);
                this.writer.flush();
                //tunggu balasan dari server apakah username tersebut sedang login atau tidak
                String exist = this.reader.readUTF();
                if (!exist.equals(constants.clientExist)) {
                    return "You haven't Login Yet";
                }
                if (this.parentDir != null) {
                    directoryHelper dh = new directoryHelper(parentDir);
                    filesystem fs = dh.getListFileSystem();
                    //save file system

                    // <editor-fold defaultstate="collapsed" desc="config lama">
                    //File f = new File("object.data");
                    //fs.save(f);
                    //dapatkan panjang byte yang dimilki oleh file tersebut
                    //byte[] fileByte = directoryHelper.getBytesFromFile(f);


                    /*byte[] fileByte = directoryHelper.toBytes(fs);
                    int fileLength = fileByte.length;
                    //kirimkan panjang file tersebut kepada server
                    debug.debugCode("Mengirim Panjang File yang akan diterima Oleh server sepanjang: " + fileLength);
                    this.writer.writeInt(fileLength);
                    this.writer.flush();
                    //menunggu balasan dari server apakah server siap untuk menerima koneksi
                    firstReply = this.reader.readUTF();
                    debug.debugCode("Menerima Pesan dari Server: " + firstReply);*/
                    // </editor-fold>

                    //balas bahwa client siap untuk menerima file dari server
                    debug.debugCode("Mengirim Pesan ke Server yaitu : " + constants.readyToGetFile);
                    this.writer.writeUTF(constants.readyToGetFile);
                    this.writer.flush();

                    //karena udah beres bikin koneksi baru untuk menghandle upload dan download
                    clientHandler downloadMode = new clientHandler(constants.DOWNLOAD_MODE, this.username, this.password, this.parentDir, this.serverAddr, this.port);
                    clientHandler uploadMode = new clientHandler(constants.UPLOAD_MODE, this.username, this.password, this.parentDir, this.serverAddr, this.port);
                    Thread downloadThread = new Thread(downloadMode);
                    Thread uploadThread = new Thread(uploadMode);
                    downloadThread.start();
                    uploadThread.start();

                }
            } else {
                debug.debugCode("Server not ready, trying to reconnect");
                if (connectAttemp++ < constants.maxConnectAttemp) {
                    close();
                    startCommunication();
                }

            }


            return ret;
        } catch (SocketException ex) {
            throw ex;
        } catch (IOException ex) {
            throw ex;
        } catch (Exception ex) {
            throw ex;
        }
    }

    private synchronized String download() throws SocketException, IOException, Exception {
        String ret = null;
        try {
            //kirim pesan pertama kali ke server
            debug.debugCode("Mengirim Pesan Pertama Ke Server yaitu: " + constants.clientDownloadMode);
            this.writer.writeUTF(constants.clientDownloadMode);
            this.writer.flush();
            //tunggu balasan dari server berupa server ready ato tidak
            ret = this.reader.readUTF();
            if (ret.equals(constants.serverReady)) {
                //kirim username dari username
                this.writer.writeUTF(this.username);
                flush();
                //tunggu balasan dari server berupa udah login apa lum
                ret = this.reader.readUTF();
                if (ret.indexOf(constants.clientExist) != -1) {
                    //dapatkan panjang byte
                    int startidx = ret.indexOf("#");
                    int length = Integer.parseInt(ret.substring(startidx + 1));
                    byte[] hasil = new byte[length];
                    this.reader.read(hasil);
                    filesystem fromServer = (filesystem) directoryHelper.toObject(hasil);
                    if (fromServer != null) {
                        //dapatkan perbedaan antara client dan server
                        filesystem diff = directoryHelper.getFileSystemDifferent(fromServer, this.clientFS);


                        //buat dulu semua direktory yang ada di diff dn tidak ada di client
                        while (true) {
                            int byteLength;
                            byte[] fsByte;
                            if (diff.directory.isEmpty() && diff.files.isEmpty()) {
                                //do nothing                                
                                this.writer.writeUTF("asdkljasld");
                                this.writer.flush();
                                //tunggu balasan file system baru
                                byteLength = this.reader.readInt();
                                fsByte = new byte[byteLength];
                                this.reader.read(fsByte);
                                fromServer = (filesystem) directoryHelper.toObject(fsByte);
                                diff = directoryHelper.getFileSystemDifferent(fromServer, this.clientFS);

                            } else {
                                //bikin foldernya dulu kk
                                directoryHelper dh = new directoryHelper(parentDir);
                                dh.createAllDirs(diff, parentDir.getAbsolutePath());
                                //trus download semua filenya
                                //request ke server tentang file yang ingin di donwload
                                //bikin list of file
                                //kirim bahwa si client mau download
                                debug.debugCode("Clien Kirim Pesan Daftar mau download");
                                this.writer.writeUTF(constants.readyDownload);
                                this.writer.flush();
                                List<String> requiredFile = dh.getListFile(diff, "");
                                //kirim daftar file yang ingin di download
                                for (String s : requiredFile) {
                                    this.writer.writeUTF(s);
                                    this.writer.flush();
                                    //tunggu panjang byte yang ingin dibuat
                                    debug.debugCode("Mendownload File: " + s);
                                    byteLength = this.reader.readInt();
                                    if (byteLength > 0) {
                                        //byte[] fileByte = new byte[byteLength];
                                        //siap2 buatmendownload
                                        //this.reader.read(fileByte);
                                        //tulis ke file
                                        int count = byteLength / constants.maxSize;
                                        if (byteLength % constants.maxSize != 0) {
                                            count++;
                                        }
                                        byte[] temp;
                                        byte[] fileByte = new byte[0];
                                        for (int i = 0; i < count; i++) {
                                            //siap2 buat mendownload
                                            if ((i + 1) * constants.maxSize > byteLength) {
                                                temp = new byte[byteLength - i * constants.maxSize];
                                            } else {
                                                temp = new byte[constants.maxSize];
                                            }

                                            this.reader.read(temp);
                                            //tulis ke file
                                            fileByte = debug.appendByte(fileByte, temp);
                                        }
                                        fileProcessor fp = new fileProcessor(this.parentDir.getAbsolutePath() + "\\" + s);
                                        //fp.writeToFile(byteFromClient);
                                        //fileProcessor fp = new fileProcessor(this.parentDir.getAbsolutePath() + "\\" + s);
                                        fp.writeToFile(fileByte);

                                    }
                                }
                                //kirim bahwa sudah selese nyari download
                                this.writer.writeUTF(constants.finishedDownload);
                                this.writer.flush();
                                debug.debugCode("Selesai Mendownload");
                                //tunggu balasan file system baru
                                byteLength = this.reader.readInt();
                                fsByte = new byte[byteLength];
                                this.reader.read(fsByte);
                                fromServer = (filesystem) directoryHelper.toObject(fsByte);
                                dh = new directoryHelper(parentDir);
                                this.clientFS = dh.getListFileSystem();
                                if (fromServer != null) {
                                    diff = directoryHelper.getFileSystemDifferent(fromServer, this.clientFS);
                                }

                            }
                        }



                    }
                } else {
                    debug.debugCode("Maneh Belum Login");
                    return ret;
                }
            } else {
                debug.debugCode("Server not ready, trying to reconnect");
                if (connectAttemp++ < constants.maxConnectAttemp) {
                    close();
                    startCommunication();
                }

            }


            return ret;
        } catch (SocketException ex) {
            throw ex;
        } catch (IOException ex) {
            throw ex;
        } catch (Exception ex) {
            throw ex;
        }
    }

    private synchronized void upload() throws SocketException, IOException, Exception {
        //disini akan dilakukan delete dan upload
        //cek dulu apakah ada file yang mau di delete
        //filesystem prevFileSystem = this.clientFS;
        filesystem currentFileSystem;
        byte[] byteForServer;
        int lengthForServer;
        //cek dulu apakah ada file yang berubah, yaitu si client FS sekarang jumlahnya lebih kecil
        //nanti aja
        //langsung upload dulu aja
        //mengirim pesan ke server bahwa
        try {
            debug.debugCode("Mengirim Pesan pertama kali ke server yaitu : " + constants.UPLOAD_MODE);
            this.writer.writeUTF(constants.clientUploadMode);
            //tunngu balasan dari server berupa server siap untuk download atau tidak
            String ret = this.reader.readUTF();
            if (ret.equals(constants.serverReady)) {
                //kirim username dari username
                currentFileSystem = new directoryHelper(parentDir).getListFileSystem();
                byteForServer = directoryHelper.toBytes(currentFileSystem);
                lengthForServer = byteForServer.length;
                this.writer.writeUTF(this.username + "#" + lengthForServer);
                flush();
                //tunggu balasan dari server berupa udah login apa lum
                ret = this.reader.readUTF();
                if (ret.equals(constants.clientExist)) {
                    //kirim filesystem client ke server
                    this.writer.write(byteForServer);
                    flush();
                    //tunggu permintaan download dari server
                    while (true) {
                        String ready = this.reader.readUTF();
                        if (ready.equals(constants.readyDownload)) {
                            //terima nama-nama file yang akan didownload
                            String fileName = this.reader.readUTF();
                            while (!fileName.equals(constants.finishedDownload)) {
                                //cari file yang bernama filename
                                debug.debugCode("Mencari File dengan nama :" + fileName);
                                File temp = new File(this.parentDir.getAbsolutePath() + "\\" + fileName);
                                if (temp.isFile()) {
                                    fileProcessor fp = new fileProcessor(temp);
                                    byte[] fileByte = fp.getByteFromFile();
                                    if (fileByte != null) {
                                        //delay(1000);
                                        int fileByteLength = fileByte.length;
                                        this.writer.writeInt(fileByteLength);
                                        flush();
                                        //kirim bytenya
                                        int count = fileByteLength / constants.maxSize;
                                        if (fileByteLength % constants.maxSize != 0) {
                                            count++;
                                        }
                                        byte[] tempByte;
                                        for (int i = 0; i < count; i++) {
                                            if ((i + 1) * constants.maxSize > fileByteLength) {
                                                tempByte = debug.getPartByte(fileByte, i * constants.maxSize, fileByteLength - i * constants.maxSize);
                                            } else {
                                                tempByte = debug.getPartByte(fileByte, i * constants.maxSize, constants.maxSize);
                                            }
                                            // delay(constants.delay);
                                            this.writer.write(tempByte);
                                            flush();
                                        }
                                        //this.writer.write(fileByte);
                                        //flush();
                                    } else {
                                        this.writer.writeInt(-999);
                                        flush();
                                    }
                                }
                                //baca dari client
                                fileName = this.reader.readUTF();
                            }
                            //keluar dari looping
                            debug.debugCode("Server selesai mendownload, berikan filesystem baru");
                            currentFileSystem = new directoryHelper(parentDir).getListFileSystem();
                            byteForServer = directoryHelper.toBytes(currentFileSystem);
                            debug.debugCode("mengirim Panjang file baru: " + byteForServer.length);
                            this.writer.writeInt(byteForServer.length);
                            flush();
                            this.writer.write(byteForServer);
                            flush();
                        } else {
                            //kalo ga siap berarti kirim lagi filesystemnya
                            currentFileSystem = new directoryHelper(parentDir).getListFileSystem();
                            byteForServer = directoryHelper.toBytes(currentFileSystem);
                            lengthForServer = byteForServer.length;
                            this.writer.writeInt(lengthForServer);
                            this.writer.flush();
                            this.writer.write(byteForServer);
                            flush();
                        }
                    }
                } else {
                    debug.debugCode("Maneh Belum Login");
                    return;
                }

            } else {
                debug.debugCode("Server not ready, trying to reconnect");
                if (connectAttemp++ < constants.maxConnectAttemp) {
                    close();
                    startCommunication();
                }

            }
            flush();
        } catch (SocketException ex) {
            throw ex;
        } catch (IOException ex) {
            throw ex;
        } catch (Exception ex) {
            throw ex;
        }
    }

    public void flush() throws IOException {
        this.writer.flush();
    }
    // </editor-fold>

    public void delay(int milisecond) {
        try {
            Thread.sleep(milisecond);

        } catch (InterruptedException ie) {
            System.out.println(ie.getMessage());
        }
    }

    public void run() {
        try {
            this.startCommunication();
        } catch (Exception ex) {
            Logger.getLogger(clientHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
